package com.tong.hao.common.configuration.mybatis;

import cn.buddha.jec.util.TongStringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @ClassName MybatisMapperRefresh
 * @Author 友野浩二
 * @Date 2019/12/8 17:13
 * @Description mybatis mapper.xml 刷新 工具
 * @Version 1.0
 */
@Slf4j
public class MybatisMapperRefresh {

    private SqlSessionFactory sqlSessionFactory;
    private Resource[] mapperLocationResources;
    private String packageSearchPath = "classpath*:/mapper/**/*.xml";
    private HashMap<String, Long> fileMapping = new HashMap<>();

    private static final String MYBATIS_CONFIGURATION = "com.baomidou.mybatisplus.core.MybatisConfiguration";
    private static final String MAPPED_STATEMENTS = "mappedStatements";
    private static final String CACHES = "caches";
    private static final String RESULT_MAPS = "resultMaps";
    private static final String PARAMETER_MAPS = "parameterMaps";
    private static final String KEY_GENERATORS = "keyGenerators";
    private static final String SQL_FRAGMENTS = "sqlFragments";
    private static final String LOADED_RESOURCES = "loadedResources";

    private static final String MAP = "map";
    private static final String SET = "set";

    public MybatisMapperRefresh(SqlSessionFactory sqlSessionFactory, String packageSearchPath) {
        this.sqlSessionFactory = sqlSessionFactory;
        if (!TongStringUtils.isEmpty(packageSearchPath)) {
            this.packageSearchPath = packageSearchPath;
        }
    }

    /**
     * @return java.lang.String
     * @Author 友野浩二
     * @Description // 刷新mapper.xml文件
     * @Date 2019/12/8 17:25
     * @Param []
     **/
    public String readMapperXml() {
        String refreshMsg;
        try {
            Configuration configuration = this.sqlSessionFactory.getConfiguration();

            // 1- 扫描文件
            try {
                this.scanMapperXml();
            } catch (IOException e) {
                refreshMsg = "packageSearchPath [ " + this.packageSearchPath + " ] 扫描包路径配置错误!!!";
                log.error(refreshMsg, e);
                return refreshMsg;
            }

            Collection<String> configColl = configuration.getMappedStatementNames();
            configColl.forEach(mapperStatementName -> log.info("Before MapperStatementName: {}", mapperStatementName));

            // 2- 判断文件是否发生变化
            if (isChanged()) {
                // 2-1 清理
                this.removeConfig(configuration);
                // 2-2 重新加载
                for (Resource mapperLocationResource : mapperLocationResources) {
                    try {
                        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocationResource.getInputStream(),
                                configuration, mapperLocationResource.toString(), configuration.getSqlFragments());
                        xmlMapperBuilder.parse();
                        log.info("Mapper文件[" + mapperLocationResource.getFilename() + "]缓存加载成功!");
                    } catch (IOException e) {
                        log.error("Mapper文件[" + mapperLocationResource.getFilename() + "]不存在或内容不对!", e);
                    }
                }
            }

            configColl.forEach(mapperStatementName -> log.info("After MapperStatementName: {}", mapperStatementName));
            refreshMsg = "刷新mybatis的mapper.xml成功!";
        } catch (Exception e) {
            refreshMsg = "刷新mybatis的mapper.xml失败!";
            log.error(refreshMsg);
        }
        return refreshMsg;
    }

    /**
     * @Author 友野浩二
     * @Description // 扫描mapper.xml文件所在路径
     * @Date 2019/12/8 17:28
     * @Param []
     * @return void
     */
    private void scanMapperXml() throws IOException {
        this.mapperLocationResources = new PathMatchingResourcePatternResolver().getResources(this.packageSearchPath);
    }

    /**
     * @Author 友野浩二
     * @Description // 判断文件是否发生变化
     * @Date 2019/12/8 17:38
     * @Param []
     * @return boolean
     */
    private boolean isChanged() throws IOException {
        boolean isChanged = false;

        for (Resource resource : this.mapperLocationResources) {
            String resourceFilename = resource.getFilename();

            // 新增标识
            boolean addFlag = !this.fileMapping.containsKey(resourceFilename);

            // 修改文件: 判断文件内容是否发生变化
            Long compareFrame = this.fileMapping.get(resourceFilename);
            long lastFrame = resource.contentLength() + resource.lastModified();
            // 修改标识
            boolean modifyFlag = null != compareFrame && compareFrame != lastFrame;

            // 新增 或 修改 时, 存储文件
            if (addFlag || modifyFlag) {
                // 文件内容帧值
                this.fileMapping.put(resourceFilename, lastFrame);
                isChanged = true;
            }
        }

        return isChanged;
    }

    /**
     * @Author 友野浩二
     * @Description // 清空Configuration中几个重要的缓存
     * @Date 2019/12/8 20:50
     * @Param [configuration]
     * @return
     */
    private void removeConfig(Configuration configuration) throws NoSuchFieldException, IllegalAccessException {
        Class<?> classConfig = configuration.getClass();

        clearConfig(classConfig, configuration, MAPPED_STATEMENTS, MAP);
        clearConfig(classConfig, configuration, CACHES, MAP);
        clearConfig(classConfig, configuration, RESULT_MAPS, MAP);
        clearConfig(classConfig, configuration, PARAMETER_MAPS, MAP);
        clearConfig(classConfig, configuration, KEY_GENERATORS, MAP);
        clearConfig(classConfig, configuration, SQL_FRAGMENTS, MAP);

        clearConfig(classConfig, configuration, LOADED_RESOURCES, SET);
    }

    private void clearConfig(Class<?> classConfig, Configuration configuration, String fieldName, String msFlag)
            throws NoSuchFieldException, IllegalAccessException {
        Field field;
        if (MYBATIS_CONFIGURATION.equals(configuration.getClass().getName())) {
            field = classConfig.getSuperclass().getDeclaredField(fieldName);
        } else {
            field = classConfig.getDeclaredField(fieldName);
        }
        field.setAccessible(true);
        switch (msFlag) {
            case MAP:
                Map mapConfig = (Map) field.get(configuration);
                mapConfig.clear();
                break;
            case SET:
                Set setConfig = (Set) field.get(configuration);
                setConfig.clear();
                break;
            default:
                log.info("clearConfig error!!!");
                break;
        }
    }

}
